package com.chunlin.list.arraylist;

import java.io.File;
import java.lang.reflect.Field;
import java.util.*;

public class ArrayListTest {
    //【说明】
    //1. ArrayList是以泛型方式实现的，使用时必须要先实例化
    //2. ArrayList实现了RandomAccess接口，表明ArrayList支持随机访问
    //3. ArrayList实现了Cloneable接口，表明ArrayList是可以clone的
    //4. ArrayList实现了Serializable接口，表明ArrayList是支持序列化的
    //RandomAccess、Cloneable 和 Serializable 都是标记接口（Marker Interface）
    //标记接口本身没有定义任何方法或字段，其作用主要是作为一种标记，表示实现这些接口的类具有某种特性或行为。这种标记通常用于给Java编译器或运行时环境传达一些信息，以便进行特定的处理或优化
    //实现标记接口的类可以通过编译器或运行时环境的特殊处理，提供或启用某些功能，例如克隆、序列化和优化的随机访问
    //没有实现标记接口的影响：如果类没有实现相应的标记接口，将无法启用或使用这些特性。例如，没有实现 Cloneable 的类调用 clone 方法会抛出异常，没有实现 Serializable 的类不能被序列化
    //5. 和Vector不同，ArrayList不是线程安全的，在单线程下可以使用，在多线程中可以选择Vector(保证线程安全)或者CopyOnWriteArrayList(读的时候不阻塞,写的时候写时拷贝(原子操作))
    //6. ArrayList底层是一段连续的空间，并且可以动态扩容，是一个动态类型的顺序表

    //ArrayList的构造
    public static void test1() {
        // ArrayList创建，推荐写法
        // 构造一个空的列表
        List<Integer> list1 = new ArrayList<>();
        for (Integer e : list1) {
            System.out.println(e);
        }

        //构造一个具有10个容量的列表
        List<Integer> list2 = new ArrayList<>(10);
        list2.add(1);
        list2.add(2);
        list2.add(3);
//        list2.add("1");
        for (Integer e : list2) {
            System.out.println(e);
        }

        // list3构造好之后，与list2中的元素一致
        List<Integer> list3 = new ArrayList<>(list2);
        for (Integer e : list3) {
            System.out.println(e);
        }

        // 避免省略类型，否则：任意类型的元素都可以存放
        List list4 = new ArrayList();
        list4.add(1);
        list4.add(2);
        list4.add("3");
        //使用Object来作为类型
        for (Object e : list4) {
            System.out.println(e);
        }
    }

    ////ArrayList的常见方法
    public static void test2() {
        //boolean add(E e)
        // 尾插 e
        List<String> list1 = new ArrayList<>();
        list1.add("JavaSE");
        list1.add("JavaWeb");
        list1.add("JVM");
        list1.add("JavaEE");
        list1.add("自动化测试");
        System.out.println(list1);

        // 获取list中有效元素个数
        System.out.println(list1.size());

        // 获取和设置index位置上的元素，注意index必须介于[0, size)间
        System.out.println(list1.get(1));
        list1.set(1, "JavaWEB");
        System.out.println(list1.get(1));

        // 在list的index位置插入指定元素，index及后续的元素统一往后搬移一个位置
        list1.add(1, "Java数据结构");
        System.out.println(list1);

        // 删除指定元素，找到了就删除，该元素之后的元素统一往前搬移一个位置
        list1.remove("JVM");
        System.out.println(list1);

        // 删除list中index位置上的元素，注意index不要超过或者等于list中有效元素个数,否则会抛出下标越界异常
        list1.remove(list1.size() - 1);
        System.out.println(list1);

        // 检测list中是否包含指定元素，包含返回true，否则返回false
        if (!list1.contains("自动化测试")) {
            list1.add("自动化测试");
        }
        System.out.println(list1);

        // 查找指定元素第一次出现的位置：indexOf从前往后找，lastIndexOf从后往前找
        list1.add("JavaSE");
        System.out.println(list1.indexOf("JavaSE"));
        System.out.println(list1.lastIndexOf("JavaSE"));

        // 使用list中[1, 3)之间的元素构成一个新的SubList返回,但是和ArrayList共用一个elementData数组
        List<String> ret = list1.subList(1, 3);
        System.out.println(ret);

        //boolean addAll(Collection<? extends E> c)
        // 尾插 c 中的元素
        List<String> list2 = new ArrayList<>(ret);
        list1.addAll(list2);
        System.out.println(list1);

        //当你创建一个子列表（通过 subList 方法）并在其上进行修改时，这些修改是反映在原列表上的
        // 然而，如果你对原列表进行结构性修改（如 addAll 操作），子列表就会变得不一致，这会导致 ConcurrentModificationException
//        list1.addAll(1, ret);
//        System.out.println(list1);
        list1.addAll(1, list2);
        System.out.println(list1);

        //void clear()
        // 清空
        list1.clear();
        System.out.println(list1.size());

    }

    //ArrayList的遍历
    //rrayList 可以使用三方方式遍历：for循环+下标、foreach、使用迭代器
    public static void test3() {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);

        // 使用下标+for遍历
        for (int i = 0; i < list.size(); ++i) {
            System.out.println(list.get(i) + " ");
        }
        System.out.println();

        // 借助foreach遍历
        for (Integer e : list) {
            System.out.println(e + " ");
        }
        System.out.println();

        //使用使用迭代器遍历
        Iterator<Integer> it = list.listIterator();
        while (it.hasNext()) {
            System.out.println(it.next() + " ");
        }
    }

    public static void printArrayListDetails(ArrayList<?> list) throws NoSuchFieldException, IllegalAccessException {
        Field elementDataField = ArrayList.class.getDeclaredField("elementData");
        //Java 9及以后版本中由于模块系统的引入,限制了对某些内部API的访问
        //代码可以编译成功,只是运行时抛出异常,通过在命令行中执行如下的命令,可以成功开启限制
        //在/target/classes路径下执行如下命令
        //java --add-opens java.base/java.util=ALL-UNNAMED -cp . com.chunlin.list.arraylist.ArrayListTest
        //java: 启动Java应用程序的命令
        //--add-opens: JVM 参数，用于开放模块系统中的某些包，使其可以通过反射进行访问。
        //java.base/java.util: 指定要开放的包，这里是 java.base 模块中的 java.util 包。
        //=ALL-UNNAMED: 允许所有未命名的模块（即没有明确在模块系统中命名的模块，如传统的类路径）访问指定的包。
        //-cp .: 设置类路径为当前目录
        elementDataField.setAccessible(true);
        Object[] elementData = (Object[]) elementDataField.get(list);

        System.out.println("ArrayList size: " + list.size());
        System.out.println("ArrayList capacity: " + elementData.length);

        //下面我来说说ArrayList的扩容过程
        //1.当你向 ArrayList 添加一个元素或多个元素时，首先会调用 add 方法，这个方法会检查是否需要扩容
        //2.然后调用add中的ensureCapacityInternal  方法用来确定当前的容量是否足够，如果不够就准备扩容
        //3.ensureCapacityInternal中调用calculateCapacity来计算所需的最小容量。如果当前数组是空的（默认空数组），则使用默认容量和所需最小容量中的较大值
        //ensureCapacityInternal中调用完calculateCapacity得到一个最小容量之后,继续调用ensureExplicitCapacity
        //4.ensureExplicitCapacity 方法检查是否需要扩容，如果需要就调用 grow 方法
        //5.grow 方法负责实际进行扩容。它会计算新的容量，然后创建一个新的数组，并将旧数组的内容复制到新数组中
        //真正扩容之前检测是否能扩容成功，防止太大导致扩容失败,如果新容量超过了 MAX_ARRAY_SIZE，则调用 hugeCapacity 进行处理
        //如果最小容量大于了默认的扩容容量(1.5倍),那么就按照用户所需大小(最小容量)扩容
    }

    public static void test4() throws NoSuchFieldException, IllegalAccessException {
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 500; ++i) {
            list.add(i);
            printArrayListDetails(list);
        }
    }

    public static final String[] SUITS = {"♠", "♥", "♣", "♦"};

    //买一副牌
    private static List<Card> buyDeck() {
        List<Card> deck = new ArrayList<>(52);

        for (int i = 0; i < 4; ++i) {
            for (int j = 1; j <= 13; ++j) {
                String suit = SUITS[i];
                int rank = j;
                Card card = new Card();
                card.rank = rank;
                card.suit = suit;

                deck.add(card);
            }
        }
        return deck;
    }

    //交换两张牌的位置
    private static void swap(List<Card> deck, int i, int j) {
        Card temp = deck.get(i);
        deck.set(i, deck.get(j));
        deck.set(j, temp);
    }

    //洗牌
    private static void shuffle(List<Card> deck) {
        Random random = new Random();
        for (int i = deck.size() - 1; i > 0; --i) {
            int j = random.nextInt(i + 1);
            swap(deck, i, j);
        }
    }

    public static void test5() {
        List<Card> deck = buyDeck();
        System.out.println("刚买回来的牌");
        System.out.println(deck);
        System.out.println("洗过的牌");
        shuffle(deck);
        System.out.println(deck);
        // 三个人，每个人轮流抓 5 张牌
        List<List<Card>> hands = new ArrayList<>();
        hands.add(new ArrayList<>());
        hands.add(new ArrayList<>());
        hands.add(new ArrayList<>());

        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 5; ++j) {
                hands.get(i).add(deck.remove(0));
            }
        }

        System.out.println("剩余手牌");
        System.out.println(deck);
        System.out.println("A中的手牌");
        System.out.println(hands.get(0));
        System.out.println("B中的手牌");
        System.out.println(hands.get(1));
        System.out.println("C中的手牌");
        System.out.println(hands.get(2));

    }

    //1.ArrayList底层使用连续的空间，任意位置插入或删除元素时，需要将该位置后序元素整体往前或者往后搬移，故时间复杂度为O(N)
    //2. 增容需要申请新空间，拷贝数据，释放旧空间。会有不小的消耗
    //3. 增容一般是呈1.5倍的增长，势必会有一定的空间浪费。例如当前容量为100，满了以后增容到150，我们再继续插入了5个数据，后面没有数据插入了，那么就浪费了45个数据空间
    public static void test6() {
    }

    public static void test7() {
    }

    public static void test8() {
    }

    public static void test9() {
    }


    public static void main(String[] args) {
//        test1();
//        test2();
//        test3();
//        try {
//            test4();
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
        test5();
//        test6();
//        test7();
//        test8();
//        test9();
    }

}
